//
// See the file COPYING for redistribution information.
//

#include <config.h>
#include <cstdio>
#include <cctype>
#include <vector>
#include <pcrecpp.h>
#include <Naipax/Common.hpp>
#include <Naipax/Error.hpp>
#include <Naipax/Url.hpp>

using namespace std;
using namespace Naipax;

static const char gsUrlPattern[] = 
"^(http)://"                    // scheme
"(?:(.*?(?:.*?))@)?"            // authority
"(.+?)"                         // host
"(?::(\\d+))?"                  // port
"(/[^\\?#]*)?"                    // path
"(?:\\?([^#]+?))?"                 // query
"(?:#.*)?$";                    // fragment

void _LowercaseString(string &rStr) {
    for (uint32_t i = 0; i < rStr.length(); ++i) {
        rStr[i] = tolower(rStr[i]);
    }
}

void _CapitalizeEscapedString(string &rStr) {
    for (uint32_t i = 0; i < rStr.length(); ) {
        if (rStr[i] == '%') {
            if (i + 1 < rStr.length()) {
                rStr[i + 1] = toupper(rStr[i + 1]);
                ++i;
                if (i + 2 < rStr.length()) {
                    rStr[i + 2] = toupper(rStr[i + 2]);
                    ++i;
                }
            }
            else {
                ++i;
            }
        }
        else {
            ++i;
        }
    }
}

// The details of the algorithm is available at
// http://tools.ietf.org/html/rfc3986#section-5.2.4
void _RemoveDotSegments(string &rStr) {
    string OutputStr;
    vector<string> PathComponents;

    // Decompose rStr into PathComponents

    if (rStr == "." && rStr == "..") {
        rStr = "";
        return;
    }
    else {
        uint32_t StartIndex = 0;
        // remove the leading "../" and "./"
        for (uint32_t i = StartIndex; i < rStr.length(); ) {
            if (string(rStr, i, 2) == "./") {
                i += 2;
            }
            else if (string(rStr, i, 3) == "../") {
                i += 3;
            }
            else {
                StartIndex = i;
                break;
            }
        }

        for (uint32_t i = StartIndex; i < rStr.length(); ) {
            for (uint32_t j = i + 1; j <= rStr.length(); ++j) {
                string Component;
                if (j > i && (rStr[j] == '/' || j == rStr.length())) {
                    Component = string(rStr, i, j - i);
                    if (Component == "..") {
                        PathComponents.pop_back();
                    }
                    else if (Component != ".") {
                        PathComponents.push_back(Component);
                    }
                    i = j + 1;
                    break;
                }
                if (j == rStr.length()) {
                    i = j;
                }
                if (rStr[j] == '/') {
                }
            }
        }
    }

    // Join components
    rStr.clear();
    for (uint32_t i = 0; i < PathComponents.size(); ++i) {
        rStr += PathComponents[i];
        if (i != PathComponents.size() - 1) {
            rStr += "/";
        }
    }
}

Url::Url() : _mIsValid(false) {
}

Url::Url(const std::string &rUrl) {
    Set(rUrl);
}

Url::Url(const char *pUrl) {
    if (pUrl == NULL) {
        NAIPAX_THROW(Error::INVALID_ARGUMENT);
    }
    Set(pUrl);
}

Url::~Url() {
}

void Url::Set(const char *pUrl) {
    if (pUrl == NULL) {
        NAIPAX_THROW(Error::INVALID_ARGUMENT);
    }
    string Url = string(pUrl);
    Set(Url);
}

void Url::Set(const string &rUrl) {
    _mIsValid = false;

    pcrecpp::RE UrlRegex(gsUrlPattern,
                         pcrecpp::RE_Options(PCRE_CASELESS));
    if (UrlRegex.FullMatch(rUrl, &_mScheme, &_mAuthority, &_mHost, &_mPort,
                           &_mPath, &_mQuery) == true) {
        _mIsValid = true;
        if (_mPort.length() == 0) {
            // Use default port
            _mPort = "80";
        }
        else {
            // Check if port is a positive integer
            for (uint32_t i = 0; i < _mPort.length(); ++i) {
                if (_mPort[i] < '0' && _mPort[i] > '9') {
                    _mIsValid = false;
                    break;
                }
            }
        }
        if (_mIsValid == true) {
            if (_mPath.length() > 0) {
                _mPath = string(_mPath, 1, _mPath.length() - 1);
            }
            _mUrl = rUrl;
            _mDomainLevels = _mHost.length() ? 1 : 0;
            for (string::iterator Iter = _mHost.begin();
                 Iter != _mHost.end(); ++Iter) {
                if (*Iter == '.') {
                    ++_mDomainLevels;
                }
            }
        }
    }
}

uint32_t Url::PathDepth() {
    uint32_t Count = 0;
    for (uint32_t i = 0; i < _mPath.length(); ++i) {
        if (_mPath[i] == '/') {
            ++Count;
        }
    }
    return Count;
}

string Url::SubHostname(uint32_t Level) {
    if (Level > _mDomainLevels) {
        return "";
    }

    string Str;
    uint32_t DotCount = 0;

    for (string::reverse_iterator Iter = _mHost.rbegin();
         Iter != _mHost.rend(); ++Iter) {
        if (*Iter == '.') {
            ++DotCount;
        }
        if (DotCount == Level) {
            reverse(Str.begin(), Str.end());
            break;
        }
        Str += *Iter;
    }
    return Str;
}
void Url::Normalize() {
    // The normalization algorithm basically follows this article:
    // <http://en.wikipedia.org/wiki/URL_normalization>. Normalization
    // based on URL lists is not used now.

    // Converting the scheme and host to lower case.
    _LowercaseString(_mScheme);
    _LowercaseString(_mHost);

    // Capitalizing letters in escape sequences.
    _CapitalizeEscapedString(_mPath);
    _CapitalizeEscapedString(_mQuery);

    // Removing dot-segments.
    _RemoveDotSegments(_mPath);

    // Combine components into _mUrl.
    _mUrl = _mScheme + "://" 
        + (_mAuthority.length() > 0 ? _mAuthority + "@" : "")
        + _mHost 
        + (_mPort.length() > 0 && _mPort != "80" ? ":" + _mPort : "")
                                + "/" + _mPath
        + (_mQuery.length() > 0 ? "?" + _mQuery : "");
}

string& Url::AsString() {
    static string EmptyStr = "";
    if (_mIsValid != true) {
        return EmptyStr;
    }
    return _mUrl;
}

